#ifndef SMART_PTR_H_
#define SMART_PTR_H_

#include <cstddef> // for NULL

// Smart pointer
template <typename T>
class SmartPtr
{
private:
	// The ref counter object
	// ref-count is increased on each new instance of
	// SmartPtr that points on a new/existing data
	class RefCounter
	{
	public:
		// C'tor
		RefCounter();

		// Get functions
		int inc() { return ++_count; }
		int dec() { return --_count; }
		int getValue() { return _count; }

	private:
		int _count;
	};

private:
	typedef T* ValPtr;
	typedef RefCounter* RefCounterPtr;

public:
	// C'tor
	explicit SmartPtr(ValPtr data = NULL);

	// Copy C'tor
	SmartPtr(const SmartPtr& other);

	// D'tor
	~SmartPtr();

	// Assignment operator
	SmartPtr& operator=(const SmartPtr& other);

	ValPtr get();
	const ValPtr get() const;

	// Set new data
	void set(ValPtr value);

	// Reset the data
	void reset();

	// checks if not null
	bool hasValue();
	
	// Data access (*)
	T& operator*();
	const T& operator*() const;

	// Data access (->)
	T* operator->();
	const T* operator->() const;

	// Compare 2 SmartPtrs
	bool operator == (const SmartPtr& other) const;
	bool operator != (const SmartPtr& other) const;

protected:
	// Retrieves the ref counter object
	RefCounterPtr getRefCounter();

	// Retrieves the ref counter object
	const RefCounterPtr getRefCounter() const;

private:
	ValPtr _data;
	RefCounterPtr _refCounter;
};

template<typename T>
SmartPtr<T>::RefCounter::RefCounter() :
	_count(1)
{
	//
}

template<typename T>
SmartPtr<T>::SmartPtr(ValPtr data) :
	_data(data), _refCounter(new RefCounter())
{
	//
}

template<typename T>
SmartPtr<T>::SmartPtr(const SmartPtr& other) :
	_data(const_cast<ValPtr>(other.get())),
		_refCounter(const_cast<RefCounterPtr>(other.getRefCounter()))
{
	getRefCounter()->inc();
}

template<typename T>
SmartPtr<T>::~SmartPtr()
{
	reset();
}

template<typename T>
SmartPtr<T>& SmartPtr<T>::operator=(const SmartPtr& other)
{
	reset();
	
	_data = const_cast<ValPtr>(other.get());
	_refCounter = const_cast<RefCounterPtr>(other.getRefCounter());

	if (getRefCounter() != NULL) {
		getRefCounter()->inc();
	}

	return *this;
}

template<typename T>
typename SmartPtr<T>::ValPtr SmartPtr<T>::get()
{
	return _data;
}

template<typename T>
const typename SmartPtr<T>::ValPtr SmartPtr<T>::get() const
{
	return _data;
}

template<typename T>
void SmartPtr<T>::set(ValPtr value)
{
	reset();

	_data = value;
	_refCounter = new RefCounter();
}

template<typename T>
void SmartPtr<T>::reset()
{
	if (getRefCounter() != NULL && getRefCounter()->dec() == 0) {
		delete _data;
		delete _refCounter;
	}

	_data = NULL;
	_refCounter = NULL;
}

template<typename T>
bool SmartPtr<T>::hasValue()
{
	return _data != NULL;
}

template<typename T>
T& SmartPtr<T>::operator*()
{
	return (*get());
}

template<typename T>
const T& SmartPtr<T>::operator*() const
{
	return (*get());
}

template<typename T>
T* SmartPtr<T>::operator->()
{
	return get();
}

template<typename T>
const T* SmartPtr<T>::operator->() const
{
	return get();
}

template<typename T>
bool SmartPtr<T>::operator == (const SmartPtr& other) const
{
	return get() == other.get() &&
		getRefCounter() == other.getRefCounter();
}

template<typename T>
bool SmartPtr<T>::operator != (const SmartPtr& other) const
{
	return !(*this == other);
}

template<typename T>
typename SmartPtr<T>::RefCounterPtr SmartPtr<T>::getRefCounter()
{
	return _refCounter;
}

template<typename T>
const typename SmartPtr<T>::RefCounterPtr SmartPtr<T>::getRefCounter() const
{
	return _refCounter;
}

#endif // SMART_PTR_H_
